/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * console.cc																*
 *																			*
 * Console I/O, and simple text manipulation								*
 *																			*
\***************************************************************************/

#include <console.h>
#include <system.h>
#include <memory.h>
#include <keyboard.h>
#include <stdio.h>
#include <io.h>

// class implementation

// constructor
Console::Console()
{
	m_x = 0;
	m_y = 0;
	attrib = 0x07;
	indebugmode = false;
}

Console::~Console()
{
	// nothing to do yet
}

void Console::ClearScreen()
{
	// for a byte, it would be 80 * 25 * 2
	// for a short, it would be 80 * 25
	// for an int, it would be 80 * 25 / 2
	// for a long, it would be 80 * 25 / 2
	// for a ulonglong, it would be 80 * 25 / 4
	
	// data to fill with
	unsigned long long dat = 0x0720072007200720ULL;

	// fill via unsigned long long - 8 bytes at a time
	unsigned long long* fb = (unsigned long long*) 0xB8000;
	
	// fill it
	for( int i = 0; i < ( ( 80 * 25 ) / 4 ); i++ )
		fb[i] = dat;
}

void Console::Output( const char* data, bool debug )
{
	if( debug && indebugmode )
		Output( data );
}

void Console::Output( const char* data )
{
	// print it
	for( int i = 0; data[i] != 0; i++ )
	{
		if( m_x + 1 > 80 )
		{
			// handle newline
			m_y++;
			m_x = 0;
		}
		
		if( m_y + 1 > 24 )
		{
			// handle scrolling
			int times = m_y - 24;
			
			// loop through, scroll as much as needed
			for( int a = 0; a < times; a++ )
			{
				// shift all the memory up a row
				memcpy( (void*) (0xB8000 - 160), (char*) 0xB8000, ( 80 * 25 * 2 ) );
				
				// framebuffer for last line
				unsigned long long* fb = (unsigned long long*) (0xB8000 + ((80*24)*2));
				
				// blank the last line
				// TODO: make this more efficient
				// unsigned short = 2 bytes, 160/2 = 80
				// unsigned long long = 8 bytes, 160/8 = 20
				for( int z = 0; z < 20; z++ )
					fb[z] = 0x0720;
				
				// reset the y value
				m_y = 24;
			}
		}
		
		if( data[i] == '\n' )
		{
			m_y++;
			m_x = 0;
			continue;
		}
		
		if( data[i] == '\r' )
		{
			m_x = 0;
			continue;
		}
		
		if( data[i] == '\t' )
		{
			m_x += ( m_x % 4 );
			continue;
		}
		
		PutC( data[i] );
		
		m_x++;
	}
	
	// move the cursor
	MoveCursor();
}

int Console::Get( char* dest, int max )
{
	// gets a string from the user
	int off = 0;
	int cnt = 0;
	char c = 0;
	
	// open up the console
	int cons = open( "console", READWRITE );
	
	// keep going...
	while( true )
	{
		// move the cursor
		MoveCursor();
		
		// check that we haven't hit the limit
		if( cnt >= max )
			return cnt;

		// get the character
		c = getc( cons );
		
		// what is it?
		switch( c )
		{				
			case 0x8:
				// backspace
				
				// check that we can go back
				if( off != 0 )
				{
					// we can go back so take away a character
					*dest--;
					
					// and from offset
					off--;
					
					// and from count
					cnt--;
					
					// and finally change the x offset
					m_x--;
					
					// and keep going
					continue;
				}
				else
				{
					continue;
				}
				
				break;
				
			case '\t':
				// tab
				
				// handle it
				for( int i = 0; i < (m_x%4); i++ )
				{
					// print a space
					PutC( ' ' );
					
					// add a space
					*dest++ = ' ';
				}
				break;
				
			case 13:
			case 10:
				// newline, all over!
				
				// add a null character to the end of dest
				*dest++ = 0;
				
				// close our descriptor
				close( cons );
				
				// return the number of bytes read
				return cnt;
				
				// ANSI C++
				break;
			
			default:
			
				// print it
				PutC( c );
				
				// increment the offset and count
				off++; cnt++;
				
				// put onto the end of dest
				*dest++ = c;
				
				// increment x
				m_x++;
				
				break;
		}
	}

	// close our descriptor
	close( cons );
				
	// how on earth is it possible to get here?
	return 0;
}

void Console::PutC( char data )
{
	*((unsigned short*) 0xB8000 + ( m_x + ( m_y * 80 ) ) ) = ( attrib << 8 ) | data;
}

void Console::MoveCursor()
{	
	// offset
	short offset = m_x + ( m_y * 80 );
	
	// make sure that the cursor shows up as white
	*(unsigned short*) ( 0xB8000 + ( offset * 2 ) ) = ( 0x07 << 8 ) | ' ';
	
	// cursor low part to vga index reg
	outportb( 0x3D4, 0x0F );
	outportb( 0x3D5, (unsigned char) offset & 0xFF );
	
	// cursor high part to vga index reg
	outportb( 0x3D4, 0x0E );
	outportb( 0x3D5, (unsigned char) ( offset >> 8 ) & 0xFF );
}
